package com.afc.struts.xml;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.afc.struts.Action;
import com.afc.struts.asserts.ActionException;
import com.afc.struts.form.ActionForm;
import com.afc.struts.form.generator.FormGenerator;
import com.afc.struts.form.generator.FormGenerator.JerichoFormGenerator;
import com.afc.struts.mapping.ActionMapping;
import com.afc.struts.mapping.ActionMapping.DefaultActionMapping;
import com.afc.struts.mapping.ActionMappingFactory;
import com.afc.struts.utils.StringUtils;

public class XmlActionMappingFactory implements ActionMappingFactory
{

  public XmlActionMappingFactory(ServletConfig aServletConfig)
  {
    servletContext = aServletConfig;
  }


  @Override
  public ActionMapping getActionMapping(String paramAnUrl) throws ActionException
  {
    if (actionMappingsByUrlPattern == null)
    {
      actionMappingsByUrlPattern = new HashMap<>();
      try
      {
        initialize();
      }
      catch (InstantiationException | IllegalAccessException | ClassNotFoundException | DOMException | IOException | ParserConfigurationException
          | SAXException anException)
      {
        throw new ActionException("unable to initialize action mappins", anException);
      }
    }
    ActionMapping actionMapping = actionMappingsByUrlPattern.get(paramAnUrl);
    if (actionMapping == null)
    {
      throw new ActionException("Unable to map action for url " + paramAnUrl);
    }
    return actionMapping;
  }

  @SuppressWarnings("unchecked")
  protected void initialize() throws ClassNotFoundException, DOMException, InstantiationException, IllegalAccessException, IOException, ParserConfigurationException, SAXException, ActionException
  {
    initializeTags();
    for (ActionTag actionTag : actionTagsByUrlPattern.values())
    {
      Class actionClass = Class.forName(actionTag.getActionName());
      Class formClass = resolveFormClass(actionTag.getFormName(), actionTag.getOriginalUrl());
      actionMappingsByUrlPattern.put(actionTag.getUrlPattern(), new DefaultActionMapping((Action)actionClass.newInstance(), formClass, actionTag.getOriginalUrl()));
    }
  }

  @SuppressWarnings("unchecked")
  protected Class<? extends ActionForm> resolveFormClass(String aFormClassName, String anOriginalUrl) throws ClassNotFoundException, ActionException, FileNotFoundException, IOException
  {
    Class formClass = null;
    FormTag formTag = formTagsByName.get(aFormClassName);
    if (formTag != null)
    {
      formClass = Class.forName(formTag.getFormClass());
      if (!ActionForm.class.isAssignableFrom(formClass))
      {
        throw new ActionException("unable to parse unknown form class :" + formClass);
      }
    }
    else
    {
      formClass = generateFormClass(anOriginalUrl);
    }
    return formClass;
  }

  @SuppressWarnings("unchecked")
  protected Class<? extends ActionForm> generateFormClass(String anOriginalUrl) throws ClassNotFoundException, ActionException, FileNotFoundException, IOException
  {
    String[] split = anOriginalUrl.split("/");
    String fileName = split[split.length - 1];
    String fileSimpleName = fileName.substring(0, fileName.indexOf("."));
    String FormClassName = StringUtils.toUpperCaseFirstChar(fileSimpleName) + "Form";

    FormGenerator formGenerator = new JerichoFormGenerator();
    File file = new File(servletContext.getServletContext().getRealPath("WEB-INF/classes/generated"));
    file.mkdir();
    file = new File(file, FormClassName + ".class");
    String originalUrl = servletContext.getServletContext().getRealPath(anOriginalUrl);
    try (InputStream inputStream = new FileInputStream(originalUrl); OutputStream outputStream = new FileOutputStream(file);)
    {
      formGenerator.generateForm("generated/" + FormClassName, inputStream, outputStream);
    }
    return (Class<? extends ActionForm>)Class.forName("generated." + FormClassName);
  }

  protected void initializeTags() throws IOException, ParserConfigurationException, SAXException, ClassNotFoundException, DOMException, InstantiationException, IllegalAccessException, ActionException
  {
    String actionConfigurationFileName = "com/afc/struts/xml/actions.xml";
    try (InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(actionConfigurationFileName))
    {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      DocumentBuilder builder = factory.newDocumentBuilder();
      Document document = builder.parse(inputStream);

      Element racine = document.getDocumentElement();


      NodeList bases = racine.getElementsByTagName("action");
      for (int i = 0; i < bases.getLength(); i++)
      {
        Node actionNode = bases.item(i);
        NodeList actionNodeList = actionNode.getChildNodes();
        initializeActionTag(actionNodeList);
      }


      bases = racine.getElementsByTagName("form");
      for (int i = 0; i < bases.getLength(); i++)
      {
        Node actionNode = bases.item(i);
        NodeList actionNodeList = actionNode.getChildNodes();
        initializeFormTag(actionNodeList);
      }
    }
  }

  protected void initializeActionTag(NodeList anActionNodeList) throws ClassNotFoundException, DOMException, InstantiationException, IllegalAccessException, ActionException
  {
    ActionTag actionTag = new ActionTag();

    for (int j = 0; j < anActionNodeList.getLength(); j++)
    {
      Node enfant = anActionNodeList.item(j);

      if (enfant.getNodeName().equals("action-name"))
      {
        actionTag.setActionName(enfant.getTextContent());
      }
      else if (enfant.getNodeName().equals("url-pattern"))
      {
        actionTag.setUrlPattern(enfant.getTextContent());
      }
      else if (enfant.getNodeName().equals("original-url"))
      {
        actionTag.setOriginalUrl(enfant.getTextContent());
      }
      else if (enfant.getNodeName().equals("form-name"))
      {
        actionTag.setFormName(enfant.getTextContent());
      }
    }
    actionTagsByUrlPattern.put(actionTag.getUrlPattern(), actionTag);
  }

  protected void initializeFormTag(NodeList aFormNodeList) throws ClassNotFoundException, DOMException, InstantiationException, IllegalAccessException, ActionException

  {
    FormTag formTag = new FormTag();

    for (int j = 0; j < aFormNodeList.getLength(); j++)
    {
      Node enfant = aFormNodeList.item(j);

      if (enfant.getNodeName().equals("form-class"))
      {
        formTag.setFormClass(enfant.getTextContent());
      }
      else if (enfant.getNodeName().equals("form-name"))
      {
        formTag.setFormName(enfant.getTextContent());
      }
    }
    formTagsByName.put(formTag.getFormName(), formTag);
  }


  protected Map<String, ActionMapping> actionMappingsByUrlPattern;
  protected Map<String, ActionTag> actionTagsByUrlPattern = new HashMap<>();
  protected Map<String, FormTag> formTagsByName = new HashMap<>();
  protected ServletConfig servletContext;

  protected class FormTag
  {
    protected String formClass;
    protected String formName;

    /**
     * @return the formClass
     */
    public String getFormClass()
    {
      return formClass;
    }
    /**
     * @param paramFormClass
     *          the formClass to set
     */
    public void setFormClass(String paramFormClass)
    {
      formClass = paramFormClass;
    }
    /**
     * @return the formName
     */
    public String getFormName()
    {
      return formName;
    }
    /**
     * @param paramFormName
     *          the formName to set
     */
    public void setFormName(String paramFormName)
    {
      formName = paramFormName;
    }


  }

  protected class ActionTag
  {

    protected String actionName;
    protected String urlPattern;
    protected String originalUrl;
    protected String formName;


    /**
     * @return the urlPattern
     */
    public String getUrlPattern()
    {
      return urlPattern;
    }
    /**
     * @param paramUrlPattern
     *          the urlPattern to set
     */
    public void setUrlPattern(String paramUrlPattern)
    {
      urlPattern = paramUrlPattern;
    }
    /**
     * @return the formName
     */
    public String getFormName()
    {
      return formName;
    }
    /**
     * @param paramFormName
     *          the formName to set
     */
    public void setFormName(String paramFormName)
    {
      formName = paramFormName;
    }
    /**
     * @return the actionName
     */
    public String getActionName()
    {
      return actionName;
    }
    /**
     * @param paramActionName
     *          the actionName to set
     */
    public void setActionName(String paramActionName)
    {
      actionName = paramActionName;
    }

    /**
     * @return the originalUrl
     */
    public String getOriginalUrl()
    {
      return originalUrl;
    }
    /**
     * @param paramOriginalUrl
     *          the originalUrl to set
     */
    public void setOriginalUrl(String paramOriginalUrl)
    {
      originalUrl = paramOriginalUrl;
    }
  }

}
